# plugs/webserver.py
#
#

""" allow commands to be called through a web server """

__copyright__ = 'this file is in the public domain'
__credits__ = 'thanks to John Munn (jrmunn@home.com) for his form focus \
javascript'

from gozerbot.thr import start_new_thread
from gozerbot.generic import rlog, handle_exception
from gozerbot.commands import cmnds
from gozerbot.persist import Persist
from gozerbot.datadir import datadir
from gozerbot.plugins import plugins
from gozerbot.myimport import my_import
from gozerbot.users import users
from gozerbot.plughelp import plughelp
from gozerbot.examples import examples
from SocketServer import ThreadingMixIn
from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
from urllib import unquote_plus
from cgi import escape
import time, sys, select

plughelp.add('webserver', 'maintain the bots webserver')

web = Persist(datadir + '/web')
if not web.data:
    web.data = {}
if not web.data.has_key('webenable'):
    web.data['webenable'] = 0
if not web.data.has_key('webport'):
    web.data['webport'] = 8088
if not web.data.has_key('webhost'):
    web.data['webhost'] = None
if not web.data.has_key('whitelist'):
    web.data['whitelist'] = []
if not web.data.has_key('whitelistenable'):
    web.data['whitelistenable'] = 1
if not web.data.has_key('blacklist'):
    web.data['blacklist'] = []
doit =  web.data['webenable']
if not doit:
    rlog(10, 'webserver', 'not enabled')
else:
    if not users.getname('web@web'):
        users.add('web', ['web@web', ], ['WEB', ])
webhostname = web.data['webhost']
webport = web.data['webport']

def init():
    """ init webserver plugin """
    global httpd
    try:
        if doit:
            httpd = BotHTTPServer((webhostname, webport), BotHTTPRequestHandler)
    except Exception, ex:
        rlog(10, 'webserver', "can't start server: %s" % str(ex))
        return 0
    if httpd:
        web = my_import('web')
        for i in web.__all__:
            try:
                httpd.reloadhandler(i)
            except:
                handle_exception()
        start_new_thread(httpd.run, ())
    return 1
    
def shutdown():
    """ shutdown webserver plugin """
    global httpd
    if not httpd:
        return 1
    try:
        httpd.stop = True
        httpd.server_close()
        time.sleep(2)
    except:
        handle_exception()
        pass
    return 1

class BotHTTPServer(ThreadingMixIn, HTTPServer):

    """ bots web server """

    allow_reuse_address = True
    daemon_thread = True

    def __init__(self, addr, handler):
        self.stop = False
        self.addr = addr
        self.handlers = {}
        self.webmods = {}
        self.whitelist = []
        HTTPServer.__init__(self, addr, handler)
        self.poll = select.poll()
        self.poll.register(self.socket)

    def addhandler(self, txt, handler):
        """ add a web handler """
        self.handlers[txt] = handler
        rlog(0, 'webserver', '%s handler added' % txt)

    def reloadhandler(self, mod):
        """ reload web handler """
        try:
            module = sys.modules['web.%s' % mod]
            reload(module)
            self.webmods[mod] = module
        except KeyError:
            self.webmods[mod] = my_import('web.%s' % mod)

    def do(self, request):
        """ do a request """
        txt = request.path[1:].split('?')[0]
        if self.handlers.has_key(txt):
            func = self.handlers[txt]
            return func(request)
        else:
            return None

    def handle_error(self, request, addr):
        """ log the error """
        rlog(10, 'webserver', 'error: %s %s' % (sys.exc_type, sys.exc_value))

    def run(self):
        """ webservers main loop """
        rlog(10, 'webserver', 'starting')
        while not self.stop:
            try:
                todo = self.poll.poll(1000)
            except:
                continue
            if todo:
                self.handle_request()
                time.sleep(0.1)
        rlog(10, 'webserver', 'stopping')

focustxt = """<HEAD>

<SCRIPT LANGUAGE="JavaScript">

<!-- This script and many more are available free online at -->
<!-- The JavaScript Source!! http://javascript.internet.com -->
<!-- John Munn  (jrmunn@home.com) -->

<!-- Begin
 function putFocus(formInst, elementInst) {
  if (document.forms.length > 0) {
   document.forms[formInst].elements[elementInst].focus();
  }
 }
// The second number in the "onLoad" command in the body
// tag determines the form's focus. Counting starts with '0'
//  End -->
</script>

</HEAD>
<BODY onLoad="putFocus(0,0);">
""" 

def webheader(request):
    """ create web header """
    request.wfile.write('<form action="dispatch">')
    request.wfile.write('Command <input name="command"></form><br>\
use chan #channel to provide a channel for channel related commands<br><br>')
    cmndslist = cmnds.list('WEB')
    cmndslist.sort()
    request.wfile.write('the bot allows the following commands: .. \
%s</p>' \
% ' '.join(cmndslist))
        
class BotHTTPRequestHandler(BaseHTTPRequestHandler):

    """ bots request handler class """

    def do_GET(self): 
        """ called on GET """
        ip = self.client_address[0]
        if ip in web.data['blacklist']:
            rlog(5, 'webserver', 'denied %s' % ip)
            self.send_error(404)
            return
        if web.data['whitelistenable'] and ip not in web.data['whitelist']:
            rlog(5, 'webserver', 'denied %s' % ip)
            self.send_error(404)
            return 
        input = unquote_plus(self.path)
        if input.endswith('&how=direct'):
            self.send_response(200)
            self.send_header('Content-type', 'text/plain')
            self.end_headers()
            self.path = self.path[:-11]
            result = self.server.do(self)
            for i in result:
                self.wfile.write("%s\n\n" % escape(i))
            return
        if input.endswith('&how=noescape'):
            self.send_response(200)
            self.send_header('Content-type', 'text/plain')
            self.end_headers()
            self.path = self.path[:-13]
            result = self.server.do(self)
            for i in result:
                self.wfile.write("%s\n" % i)
            return
        if self.path == '/':
            self.send_response(200)
            self.send_header('Content-type', 'text/html')
            self.end_headers()
            self.wfile.write(focustxt)
            self.wfile.write("<H1>PYJBOT</H1>")
            webheader(self)
            self.wfile.write("<a href='http://%s:%s/quotes'>quotes</a><br>\n" \
% self.server.addr)
            self.wfile.write("<a href='http://%s:%s/infoitems'>infoitems<br>\
</a>\n" % self.server.addr)
            self.wfile.write("<a href='http://%s:%s/karma'>karma</a><br><br>\
\n" % self.server.addr)
            return
        result = self.server.do(self)
        if result:
            self.send_response(200)
            self.send_header('Content-type', 'text/html')
            self.end_headers()
            self.wfile.write("<H1>PYJBOT</H1>")
            webheader(self)
            self.wfile.write('<h2>results</h2>')
            for i in result:
                self.wfile.write("%s<br>" % escape(i))
            self.wfile.write(focustxt)
        else:
            self.send_error(404)

    #localhost - - [18/Mar/2006 14:51:53] "GET / HTTP/1.1" 200 -
    def log_request(self, code, size='-'):
        """ log the request """
        rlog(10, 'webserver', '%s "%s %s" %s %s %s' % \
(self.address_string(), self.command, self.path, self.request_version, code ,\
size))

    def log_error(self, mask, *txt):
        """ log the error """
        rlog(10, 'webserver', self.address_string() + ' ' + mask % txt)

httpd = None

    
def handle_web(bot, ievent):
    """ web .. show on which host:port webserver is running """
    if httpd:
        ievent.reply("web server is running at http://%s:%s" % \
httpd.addr)
    else:
        ievent.reply("webserver is not running")
        
cmnds.add('web', handle_web, 'USER')
examples.add('web', 'show what web adress we are running on', 'web')

def handle_webenable(bot, ievent):
    """ web-enable <host> <port> """
    try:
        (host, port) = ievent.args
        port = int(port)
    except ValueError:
        ievent.missing('<host> <port>')
        return
    web.data['webenable'] = 1
    web.data['webhost'] = host
    web.data['webport'] = port
    web.save()
    if plugins.reload('plugs', 'webserver'):
        ievent.reply('done')
    else:
        ievent.reply('error reloading webserver plugin')
        
cmnds.add('web-enable', handle_webenable, 'OPER')
examples.add('web-enable', 'web-enable <host> <port> .. enable the \
webserver', 'web-enable localhost 8088')

def handle_webdisable(bot, ievent):
    """ web-disable .. disable webserver"""
    web.data['webenable'] = 0
    web.save()
    if plugins.reload('plugs', 'webserver'):
        ievent.reply('done')
    else:
        ievent.reply('error reloading webserver plugin')

cmnds.add('web-disable', handle_webdisable, 'OPER')
examples.add('web-disable', 'disable the webserver', 'web-disable')

def handle_webreload(bot, ievent):
    """ web-reload <handler> """
    if not httpd:
        ievent.reply('webserver is not running')
        return
    try:
        what = ievent.args[0]
    except:
        ievent.missing('<handler>')
    try:
        httpd.reloadhandler(what)
    except Exception, ex:
        handle_exception(ievent)
        return
    ievent.reply('%s reloaded' % what)

cmnds.add('web-reload', handle_webreload, 'OPER')
examples.add('web-reload', 'web-reload <handler> .. reload a web handler', \
'web-reload dispatch')

def handle_weballowip(bot, ievent):
    """ web-allowip <ipnr> """
    if not httpd:
        ievent.reply('webserver is not running')
        return
    try:
        what = ievent.args[0]
    except:
        ievent.missing('<ipnr>')
    web.data['whitelist'].append(what)
    try:
        web.data['blacklist'].remove(what)
    except ValueError:
        pass
    web.save()
    ievent.reply('%s allowed' % what)

cmnds.add('web-allowip', handle_weballowip, 'OPER')
examples.add('web-allowip', 'web-allowip <ipnr> .. add ip to whitelist and \
remove from blacklist', 'web-allowip 127.0.0.1')

def handle_webdenyip(bot, ievent):
    """ web-denyip <ipnr> """
    if not httpd:
        ievent.reply('webserver is not running')
        return
    try:
        what = ievent.args[0]
    except:
        ievent.missing('<ip>')
    if what not in web.data['blacklist']:
        web.data['blacklist'].append(what)
    try:
        web.data['whitelist'].remove(what)
    except ValueError:
        pass
    web.save()
    ievent.reply("%s denied" % what)

cmnds.add('web-denyip', handle_webdenyip, 'OPER')
examples.add('web-denyip', 'web-denyip <ipnr> .. remove from whitelist and \
add to blacklist', 'web-denyip 127.0.0.1')

def handle_weblists(bot, ievent):
    """ web-lists .. show black and white lists"""
    ievent.reply("whitelist: %s blacklist: %s" % (web.data['whitelist'], \
web.data['blacklist']))

cmnds.add('web-lists', handle_weblists, 'OPER')
examples.add('web-lists', 'show webservers white and black lists', \
'web-lists')

def handle_webdefaultallow(bot, ievent):
    """ web-defaultallow .. put webserver in default allow mode """
    web.data['whitelistenable'] = 0
    web.save()
    plugins.reload('plugs', 'webserver')
    ievent.reply('ok')

cmnds.add('web-defaultallow', handle_webdefaultallow, 'OPER')
examples.add('web-defaultallow', 'set webservers mode to defaultallow .. \
all ips except those in the blacklist', 'web-defaultallow')

def handle_webdefaultdeny(bot, ievent):
    """ web-defaultdeny .. put webserver in default deny mode """
    web.data['whitelistenable'] = 0
    web.save()
    plugins.reload('plugs', 'webserver')
    ievent.reply('ok')

cmnds.add('web-defaultdeny', handle_webdefaultdeny, 'OPER')
examples.add('web-defaultdeny', 'put webserver in default deny mode .. only \
allow ips in whitelist', 'web-defaultdeny')
